package com.wujunshen.orika;

import com.wujunshen.orika.configurer.OrikaMapperFactoryBuilderConfigurer;
import com.wujunshen.orika.configurer.OrikaMapperFactoryConfigurer;
import com.wujunshen.orika.convert.DateConverter;
import com.wujunshen.orika.convert.ValueConverter;
import com.wujunshen.orika.properties.OrikaProperties;
import com.wujunshen.orika.utils.OrikaUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory.MapperFactoryBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author frank woo(吴峻申) <br>
 * email:<a href="mailto:frank_wjs@hotmail.com">frank_wjs@hotmail.com</a> <br>
 * @date 2020/2/7 5:33 下午 <br>
 */
@Slf4j
@ConditionalOnProperty(name = "orika.enabled", matchIfMissing = true)
@EnableConfigurationProperties(OrikaProperties.class)
@Configuration
public class OrikaAutoConfiguration {
    /**
     * The configuration properties for Orika.
     */
    @Resource
    private OrikaProperties orikaProperties;
    
    /**
     * The configurers for {@link MapperFactoryBuilder}.
     */
    @Resource
    private Optional<List<OrikaMapperFactoryBuilderConfigurer>> orikaMapperFactoryBuilderConfigurers;
    
    /**
     * The configurers for {@link MapperFactory}.
     */
    @Resource
    private Optional<List<OrikaMapperFactoryConfigurer>> orikaMapperFactoryConfigurers;
    
    /**
     * Creates a {@link MapperFactoryBuilder}.
     *
     * @return a {@link MapperFactoryBuilder}.
     */
    @ConditionalOnMissingBean
    @Bean
    public MapperFactoryBuilder<?, ?> orikaMapperFactoryBuilder() {
        log.debug(
                "\nuseBuiltinConverters:{},useAutoMapping:{},"
                        + "mapNulls:{},dumpStateOnException:{},"
                        + "favorExtension:{},captureFieldContext:{}\n",
                orikaProperties.isUseBuiltinConverters(),
                orikaProperties.isUseAutoMapping(),
                orikaProperties.isMapNulls(),
                orikaProperties.isDumpStateOnException(),
                orikaProperties.isFavorExtension(),
                orikaProperties.isCaptureFieldContext());
        
        DefaultMapperFactory.Builder orikaMapperFactoryBuilder =
                new DefaultMapperFactory.Builder()
                        .useBuiltinConverters(orikaProperties.isUseBuiltinConverters())
                        .useAutoMapping(orikaProperties.isUseAutoMapping())
                        .mapNulls(orikaProperties.isMapNulls())
                        .dumpStateOnException(orikaProperties.isDumpStateOnException())
                        .favorExtension(orikaProperties.isFavorExtension())
                        .captureFieldContext(orikaProperties.isCaptureFieldContext());
        
        orikaMapperFactoryBuilderConfigurers
                .orElseGet(Collections::emptyList)
                .forEach(configurer -> configurer.configure(orikaMapperFactoryBuilder));
        
        return orikaMapperFactoryBuilder;
    }
    
    /**
     * Creates a {@link MapperFactory}.
     *
     * @param orikaMapperFactoryBuilder the {@link MapperFactoryBuilder}.
     * @return a {@link MapperFactory}.
     */
    @ConditionalOnMissingBean
    @Bean
    public MapperFactory orikaMapperFactory(MapperFactoryBuilder<?, ?> orikaMapperFactoryBuilder) {
        MapperFactory mapperFactory = orikaMapperFactoryBuilder.build();
        orikaMapperFactoryConfigurers
                .orElseGet(Collections::emptyList)
                .forEach(configurer -> configurer.configure(mapperFactory));
        
        mapperFactory
                .getConverterFactory()
                .registerConverter(orikaProperties.getDateConvert(), new DateConverter());
        
        mapperFactory
                .getConverterFactory()
                .registerConverter(orikaProperties.getValueConvert(), new ValueConverter<>());
        
        Arrays.stream(orikaProperties.getJson())
                .forEach(
                        properties -> OrikaUtils.createJsonConvert(mapperFactory, properties.getConvertName()));
        
        Arrays.stream(orikaProperties.getEnumeration())
                .forEach(
                        properties -> OrikaUtils.createEnumConvert(mapperFactory, properties.getConvertName()));
        
        Arrays.stream(orikaProperties.getZeroEnumeration())
                .forEach(
                        properties ->
                                OrikaUtils.registerZeroEnumConvert(mapperFactory, properties.getConvertName()));
        
        Arrays.stream(orikaProperties.getStringEnumeration())
                .forEach(
                        properties ->
                                OrikaUtils.registerStringEnumConvert(mapperFactory, properties.getConvertName()));
        
        return mapperFactory;
    }
}
